package com.cnemc.docker.service;

import com.cnemc.docker.pool.DockerClientPool;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.InspectContainerResponse;
import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.Frame;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;

import java.io.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Slf4j
public class DockerService {
    private final DockerClientPool dockerClientPool;

    public DockerService(DockerClientPool dockerClientPool) {
        this.dockerClientPool = dockerClientPool;
    }

    public List<Container> listContainers() {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            return dockerClient.listContainersCmd().exec();
        } catch (Exception e) {
            log.error("Failed to list containers", e);
            throw new RuntimeException("Failed to list containers", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    public CreateContainerResponse createContainer(String image, String name) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            return dockerClient.createContainerCmd(image)
                    .withName(name)
                    .exec();
        } catch (Exception e) {
            log.error("Failed to create container", e);
            throw new RuntimeException("Failed to create container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    public void startContainer(String containerId) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            dockerClient.startContainerCmd(containerId).exec();
        } catch (Exception e) {
            log.error("Failed to start container", e);
            throw new RuntimeException("Failed to start container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    public InspectContainerResponse.ContainerState stopContainer(String containerId) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            dockerClient.stopContainerCmd(containerId)
                    .withTimeout(10) // 等待10秒后强制停止
                    .exec();
            // 获取容器状态
            InspectContainerResponse inspectResponse = dockerClient.inspectContainerCmd(containerId).exec();
            return inspectResponse.getState();
        } catch (Exception e) {
            log.error("Failed to stop container: {}", containerId, e);
            throw new RuntimeException("Failed to stop container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    public InspectContainerResponse.ContainerState getContainerState(String containerId) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            InspectContainerResponse inspectResponse = dockerClient.inspectContainerCmd(containerId).exec();
            return inspectResponse.getState();
        } catch (Exception e) {
            log.error("Failed to get container state: {}", containerId, e);
            throw new RuntimeException("Failed to get container state", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 上传文件到容器
     *
     * @param containerId   容器ID
     * @param hostPath      宿主机文件路径
     * @param containerPath 容器内目标路径
     */
    public void copyToContainer(String containerId, String hostPath, String containerPath) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            dockerClient.copyArchiveToContainerCmd(containerId)
                    .withHostResource(hostPath)
                    .withRemotePath(containerPath)
                    .exec();
        } catch (Exception e) {
            log.error("Failed to copy file to container: {} -> {}", hostPath, containerPath, e);
            throw new RuntimeException("Failed to copy file to container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 通过输入流上传文件到容器
     *
     * @param containerId   容器ID
     * @param inputStream   文件输入流
     * @param fileName      目标文件名
     * @param containerPath 容器内目标路径
     */
    public void copyStreamToContainer(String containerId, InputStream inputStream, String fileName, String containerPath) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();

            // 创建TAR输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos)) {
                // 创建TAR条目
                TarArchiveEntry entry = new TarArchiveEntry(fileName);
                entry.setSize(inputStream.available());
                tarOut.putArchiveEntry(entry);

                // 写入文件内容
                byte[] buffer = new byte[8192];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    tarOut.write(buffer, 0, len);
                }
                tarOut.closeArchiveEntry();
            }

            // 上传到容器
            try (InputStream tarStream = new java.io.ByteArrayInputStream(baos.toByteArray())) {
                dockerClient.copyArchiveToContainerCmd(containerId)
                        .withTarInputStream(tarStream)
                        .withRemotePath(containerPath)
                        .exec();
            }
        } catch (IOException e) {
            log.error("Failed to copy stream to container: {} -> {}", fileName, containerPath, e);
            throw new RuntimeException("Failed to copy stream to container", e);
        } catch (Exception e) {
            log.error("Failed to copy stream to container: {} -> {}", fileName, containerPath, e);
            throw new RuntimeException("Failed to copy stream to container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 从容器复制��到宿主机
     *
     * @param containerId   容器ID
     * @param containerPath 容器内文件路径
     * @param hostPath      宿主机目标路径
     */
    public void copyFromContainer(String containerId, String containerPath, String hostPath) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();
            try (InputStream containerStream = dockerClient.copyArchiveFromContainerCmd(containerId, containerPath).exec();
                 FileOutputStream fos = new FileOutputStream(hostPath)) {

                File parentDir = new File(hostPath).getParentFile();
                if (!parentDir.exists()) {
                    parentDir.mkdirs();
                }
                byte[] buffer = new byte[8192];
                int len;
                while ((len = containerStream.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                }
            }
        } catch (Exception e) {
            log.error("Failed to copy from container: {} -> {}", containerPath, hostPath, e);
            throw new RuntimeException("Failed to copy from container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 从容器获取文件流
     *
     * @param containerId   容器ID
     * @param containerPath 容器内文件路径
     * @return 文件输入流
     */
    public InputStream getStreamFromContainer(String containerId, String containerPath) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();

            // 从容器读取数据到内存
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            try (InputStream containerStream = dockerClient.copyArchiveFromContainerCmd(containerId, containerPath).exec()) {
                byte[] buffer = new byte[8192];
                int len;
                while ((len = containerStream.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }
            }

            // 返回内存中的数据流
            return new ByteArrayInputStream(baos.toByteArray());

        } catch (Exception e) {
            log.error("Failed to get stream from container: {}", containerPath, e);
            throw new RuntimeException("Failed to get stream from container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 在容器中执行命令
     *
     * @param containerId 容器ID
     * @param command     要执行的命令及参数
     * @return 命令执行的输出结果
     */
    public String executeCommand(String containerId, String... command) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();

            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .withCmd(command)
                    .exec();

            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
            ByteArrayOutputStream stderr = new ByteArrayOutputStream();

            dockerClient.execStartCmd(execCreateCmdResponse.getId())
                    .exec(new ResultCallback.Adapter<Frame>() {
                        @Override
                        public void onNext(Frame frame) {
                            if (frame != null) {
                                try {
                                    switch (frame.getStreamType()) {
                                        case STDOUT:
                                            stdout.write(frame.getPayload());
                                            break;
                                        case STDERR:
                                            stderr.write(frame.getPayload());
                                            break;
                                        default:
                                            break;
                                    }
                                } catch (IOException e) {
                                    onError(e);
                                }
                            }
                        }
                    }).awaitCompletion(30, TimeUnit.SECONDS);

            String output = stdout.toString() + stderr.toString();

            Integer exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCodeLong().intValue();
            if (exitCode != 0) {
                log.error("Command execution failed with exit code: {}", exitCode);
                throw new RuntimeException("Command execution failed: " + output);
            }

            return output;

        } catch (Exception e) {
            log.error("Failed to execute command in container: {}", String.join(" ", command), e);
            throw new RuntimeException("Failed to execute command in container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    /**
     * 在容器中执行命令（带进度回调）
     *
     * @param containerId      容器ID
     * @param progressCallback 进度回调函数，接收命令的实时输出
     * @param command          要执行的命令及参数
     * @return 命令执行的输出结果
     */
    public String executeCommandWithProgress(String containerId, Consumer<Integer> progressCallback, String... command) {
        DockerClient dockerClient = null;
        try {
            dockerClient = dockerClientPool.borrowObject();

            ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId)
                    .withAttachStdout(true)
                    .withAttachStderr(true)
                    .withCmd(command)
                    .exec();

            ByteArrayOutputStream stdout = new ByteArrayOutputStream();
            ByteArrayOutputStream stderr = new ByteArrayOutputStream();

            // 创建进度追踪器
            ProgressTracker progressTracker = new ProgressTracker(30, progressCallback);
            progressTracker.start();

            dockerClient.execStartCmd(execCreateCmdResponse.getId())
                    .exec(new ResultCallback.Adapter<Frame>() {
                        @Override
                        public void onNext(Frame frame) {
                            if (frame != null) {
                                try {
                                    byte[] payload = frame.getPayload();
                                    switch (frame.getStreamType()) {
                                        case STDOUT:
                                            stdout.write(payload);
                                            break;
                                        case STDERR:
                                            stderr.write(payload);
                                            break;
                                        default:
                                            break;
                                    }
                                } catch (IOException e) {
                                    onError(e);
                                }
                            }
                        }

                        @Override
                        public void onComplete() {
                            super.onComplete();
                            progressTracker.complete();
                        }
                    }).awaitCompletion(30, TimeUnit.SECONDS);

            String output = stdout.toString() + stderr.toString();

            Integer exitCode = dockerClient.inspectExecCmd(execCreateCmdResponse.getId()).exec().getExitCodeLong().intValue();
            if (exitCode != 0) {
                log.error("Command execution failed with exit code: {}", exitCode);
                throw new RuntimeException("Command execution failed: " + output);
            }

            return output;

        } catch (Exception e) {
            log.error("Failed to execute command in container: {}", String.join(" ", command), e);
            throw new RuntimeException("Failed to execute command in container", e);
        } finally {
            if (dockerClient != null) {
                dockerClientPool.returnObject(dockerClient);
            }
        }
    }

    // 添加进度追踪器内部类
    private static class ProgressTracker {
        private final int totalSeconds;
        private final Consumer<Integer> progressCallback;
        private volatile boolean completed = false;
        private Thread progressThread;

        public ProgressTracker(int totalSeconds, Consumer<Integer> progressCallback) {
            this.totalSeconds = totalSeconds;
            this.progressCallback = progressCallback;
        }

        public void start() {
            progressThread = new Thread(() -> {
                long startTime = System.currentTimeMillis();
                while (!completed) {
                    long currentTime = System.currentTimeMillis();
                    long elapsedTime = currentTime - startTime;
                    int progress = (int) Math.min(100, (elapsedTime / (totalSeconds * 10.0)));

                    progressCallback.accept(progress);

                    try {
                        Thread.sleep(100); // 每100ms更新一次进度
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
                // 确保最终进度为100%
                if (completed) {
                    progressCallback.accept(100);
                }
            });
            progressThread.start();
        }

        public void complete() {
            this.completed = true;
            if (progressThread != null) {
                progressThread.interrupt();
            }
        }
    }

} 